#include "basechart_painter.h"

////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// BaseChartPainter
////////////////////////////////////////////////////////////////////////////////////////////////////

/*! \class BaseChartPainter
  \brief QPainter subclass used internally

  This QPainter subclass is used to provide some extended functionality e.g. for
  tweaking position consistency between antialiased and non-antialiased
  painting. Further it provides workarounds for QPainter quirks.

  \warning This class intentionally hides non-virtual functions of QPainter,
  e.g. setPen, save and restore. So while it is possible to pass a
  BaseChartPainter instance to a function that expects a QPainter pointer, some
  of the workarounds and tweaks will be unavailable to the function (because it
  will call the base class implementations of the functions actually hidden by
  BaseChartPainter).
*/
BaseChartPainter::BaseChartPainter()
    : mModes(pmDefault), mIsAntialiasing(false) {
  // don't setRenderHint(QPainter::NonCosmeticDefautPen) here, because painter
  // isn't active yet and a call to begin() will follow
}

/*!
  Creates a new BaseChartPainter instance on the specified paint \a device and
  sets default values. Just like the analogous QPainter constructor, begins
  painting on \a device immediately.

  Like \ref begin, this method sets QPainter::NonCosmeticDefaultPen in Qt
  versions before Qt5.
*/
BaseChartPainter::BaseChartPainter(QPaintDevice* device)
    : QPainter(device), mModes(pmDefault), mIsAntialiasing(false) {}

/*!
  Sets the pen of the painter and applies certain fixes to it, depending on the
  mode of this BaseChartPainter.

  \note this function hides the non-virtual base class implementation.
*/
void BaseChartPainter::setPen(const QPen& pen) {
  QPainter::setPen(pen);
  if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic();
}

/*! \overload

  Sets the pen (by color) of the painter and applies certain fixes to it,
  depending on the mode of this BaseChartPainter.

  \note this function hides the non-virtual base class implementation.
*/
void BaseChartPainter::setPen(const QColor& color) {
  QPainter::setPen(color);
  if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic();
}

/*! \overload

  Sets the pen (by style) of the painter and applies certain fixes to it,
  depending on the mode of this BaseChartPainter.

  \note this function hides the non-virtual base class implementation.
*/
void BaseChartPainter::setPen(Qt::PenStyle penStyle) {
  QPainter::setPen(penStyle);
  if (mModes.testFlag(pmNonCosmetic)) makeNonCosmetic();
}

/*! \overload

  Works around a Qt bug introduced with Qt 4.8 which makes drawing QLineF
  unpredictable when antialiasing is disabled. Thus when antialiasing is
  disabled, it rounds the \a line to integer coordinates and then passes it to
  the original drawLine.

  \note this function hides the non-virtual base class implementation.
*/
void BaseChartPainter::drawLine(const QLineF& line) {
  if (mIsAntialiasing || mModes.testFlag(pmVectorized))
    QPainter::drawLine(line);
  else
    QPainter::drawLine(line.toLine());
}

/*!
  Sets whether painting uses antialiasing or not. Use this method instead of
  using setRenderHint with QPainter::Antialiasing directly, as it allows
  BaseChartPainter to regain pixel exactness between antialiased and
  non-antialiased painting (Since Qt < 5.0 uses slightly different coordinate
  systems for AA/Non-AA painting).
*/
void BaseChartPainter::setAntialiasing(bool enabled) {
  setRenderHint(QPainter::Antialiasing, enabled);
  if (mIsAntialiasing != enabled) {
    mIsAntialiasing = enabled;
    if (!mModes.testFlag(pmVectorized))  // antialiasing half-pixel shift only
                                         // needed for rasterized outputs
    {
      if (mIsAntialiasing)
        translate(0.5, 0.5);
      else
        translate(-0.5, -0.5);
    }
  }
}

/*!
  Sets the mode of the painter. This controls whether the painter shall adjust
  its fixes/workarounds optimized for certain output devices.
*/
void BaseChartPainter::setModes(BaseChartPainter::PainterModes modes) {
  mModes = modes;
}

/*!
  Sets the QPainter::NonCosmeticDefaultPen in Qt versions before Qt5 after
  beginning painting on \a device. This is necessary to get cosmetic pen
  consistency across Qt versions, because since Qt5, all pens are non-cosmetic
  by default, and in Qt4 this render hint must be set to get that behaviour.

  The Constructor \ref BaseChartPainter(QPaintDevice *device) which directly
  starts painting also sets the render hint as appropriate.

  \note this function hides the non-virtual base class implementation.
*/
bool BaseChartPainter::begin(QPaintDevice* device) {
  bool result = QPainter::begin(device);
  return result;
}

/*! \overload

  Sets the mode of the painter. This controls whether the painter shall adjust
  its fixes/workarounds optimized for certain output devices.
*/
void BaseChartPainter::setMode(BaseChartPainter::PainterMode mode,
                               bool enabled) {
  if (!enabled && mModes.testFlag(mode))
    mModes &= ~mode;
  else if (enabled && !mModes.testFlag(mode))
    mModes |= mode;
}

/*!
  Saves the painter (see QPainter::save). Since BaseChartPainter adds some new
  internal state to QPainter, the save/restore functions are reimplemented to
  also save/restore those members.

  \note this function hides the non-virtual base class implementation.

  \see restore
*/
void BaseChartPainter::save() {
  mAntialiasingStack.push(mIsAntialiasing);
  QPainter::save();
}

/*!
  Restores the painter (see QPainter::restore). Since BaseChartPainter adds some
  new internal state to QPainter, the save/restore functions are reimplemented
  to also save/restore those members.

  \note this function hides the non-virtual base class implementation.

  \see save
*/
void BaseChartPainter::restore() {
  if (!mAntialiasingStack.isEmpty())
    mIsAntialiasing = mAntialiasingStack.pop();
  else
    qDebug() << Q_FUNC_INFO << "Unbalanced save/restore";
  QPainter::restore();
}

/*!
  Changes the pen width to 1 if it currently is 0. This function is called in
  the \ref setPen overrides when the \ref pmNonCosmetic mode is set.
*/
void BaseChartPainter::makeNonCosmetic() {
  if (qFuzzyIsNull(pen().widthF())) {
    QPen p = pen();
    p.setWidth(1);
    QPainter::setPen(p);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// BaseChartAbstractPaintBuffer
////////////////////////////////////////////////////////////////////////////////////////////////////

/*! \class BaseChartPaintBufferPixmap
  \brief A paint buffer based on QPixmap, using software raster rendering

  This paint buffer is the default and fall-back paint buffer which uses
  software rendering and QPixmap as internal buffer. It is used if \ref
  QCustomPlot::setOpenGl is false.
*/

/*!
  Creates a pixmap paint buffer instancen with the specified \a size and \a
  devicePixelRatio, if applicable.
*/
BaseChartAbstractPaintBuffer::BaseChartAbstractPaintBuffer(
    const QSize& size, double devicePixelRatio)
    : mSize(size), mDevicePixelRatio(devicePixelRatio), mInvalidated(true) {}

BaseChartAbstractPaintBuffer::~BaseChartAbstractPaintBuffer() {}

/*!
  Sets the paint buffer size.

  The buffer is reallocated (by calling \ref reallocateBuffer), so any painters
  that were obtained by \ref startPainting are invalidated and must not be used
  after calling this method.

  If \a size is already the current buffer size, this method does nothing.
*/
void BaseChartAbstractPaintBuffer::setSize(const QSize& size) {
  if (mSize != size) {
    mSize = size;
    reallocateBuffer();
  }
}

/*!
  Sets the invalidated flag to \a invalidated.

  This mechanism is used internally in conjunction with isolated replotting of
  \ref QCPLayer instances (in \ref QCPLayer::lmBuffered mode). If \ref
  QCPLayer::replot is called on a buffered layer, i.e. an isolated repaint of
  only that layer (and its dedicated paint buffer) is requested, QCustomPlot
  will decide depending on the invalidated flags of other paint buffers whether
  it also replots them, instead of only the layer on which the replot was
  called.

  The invalidated flag is set to true when \ref QCPLayer association has
  changed, i.e. if layers were added or removed from this buffer, or if they
  were reordered. It is set to false as soon as all associated \ref QCPLayer
  instances are drawn onto the buffer.

  Under normal circumstances, it is not necessary to manually call this method.
*/
void BaseChartAbstractPaintBuffer::setInvalidated(bool invalidated) {
  mInvalidated = invalidated;
}

/*!
  Sets the device pixel ratio to \a ratio. This is useful to render on high-DPI
  output devices. The ratio is automatically set to the device pixel ratio used
  by the parent QCustomPlot instance.

  The buffer is reallocated (by calling \ref reallocateBuffer), so any painters
  that were obtained by \ref startPainting are invalidated and must not be used
  after calling this method.

  \note This method is only available for Qt versions 5.4 and higher.
*/
void BaseChartAbstractPaintBuffer::setDevicePixelRatio(double ratio) {
  if (!qFuzzyCompare(ratio, mDevicePixelRatio)) {
    mDevicePixelRatio = ratio;
    reallocateBuffer();
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// BaseChartPaintBufferPixmap
////////////////////////////////////////////////////////////////////////////////////////////////////

/*! \class BaseChartPaintBufferPixmap
  \brief A paint buffer based on QPixmap, using software raster rendering

  This paint buffer is the default and fall-back paint buffer which uses
  software rendering and QPixmap as internal buffer. It is used if \ref
  QCustomPlot::setOpenGl is false.
*/

/*!
  Creates a pixmap paint buffer instancen with the specified \a size and \a
  devicePixelRatio, if applicable.
*/
BaseChartPaintBufferPixmap::BaseChartPaintBufferPixmap(const QSize& size,
                                                       double devicePixelRatio)
    : BaseChartAbstractPaintBuffer(size, devicePixelRatio) {
  BaseChartPaintBufferPixmap::reallocateBuffer();
}

BaseChartPaintBufferPixmap::~BaseChartPaintBufferPixmap() {}

/* inherits documentation from base class */
BaseChartPainter* BaseChartPaintBufferPixmap::startPainting() {
  BaseChartPainter* result = new BaseChartPainter(&mBuffer);
  result->setRenderHint(QPainter::Antialiasing);
  return result;
}

/* inherits documentation from base class */
void BaseChartPaintBufferPixmap::draw(BaseChartPainter* painter) const {
  if (painter && painter->isActive())
    painter->drawPixmap(0, 0, mBuffer);
  else
    qDebug() << Q_FUNC_INFO << "invalid or inactive painter passed";
}

/* inherits documentation from base class */
void BaseChartPaintBufferPixmap::clear(const QColor& color) {
  mBuffer.fill(color);
}

/* inherits documentation from base class */
void BaseChartPaintBufferPixmap::reallocateBuffer() {
  setInvalidated();
  if (!qFuzzyCompare(1.0, mDevicePixelRatio)) {
    mBuffer = QPixmap(mSize * mDevicePixelRatio);
    mBuffer.setDevicePixelRatio(mDevicePixelRatio);
  } else {
    mBuffer = QPixmap(mSize);
  }
}